package net.gazhi.delonix.rbac.web.init;

import java.lang.reflect.Method;
import java.util.TreeSet;

import javax.annotation.Resource;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import net.gazhi.delonix.core.annonation.ApiAction;
import net.gazhi.delonix.core.annonation.ApiController;
import net.gazhi.delonix.core.annonation.ApiLevel;
import net.gazhi.delonix.core.annonation.ApiModule;
import net.gazhi.delonix.core.annonation.SortedControllers;
import net.gazhi.delonix.core.init.AbstractInitService;
import net.gazhi.delonix.rbac.entity.Action;
import net.gazhi.delonix.rbac.entity.Controller;
import net.gazhi.delonix.rbac.entity.DX_TABLE;
import net.gazhi.delonix.rbac.entity.Module;

/**
 * 初始化 Module / Controller / Action
 * 
 * @author Jeffrey Lin
 *
 */
@Service
public class InitApiActionService extends AbstractInitService {

	private static final Logger logger = LogManager.getLogger();

	@Resource
	private String[] controllerPackages;

	@Override
	@Transactional
	public boolean checkAndDoInit() throws Exception {
		logger.info("正在初始化 [Action]...");

		dao.createSQLQuery("truncate table " + DX_TABLE.dx_action).executeUpdate();
		dao.createSQLQuery("truncate table " + DX_TABLE.dx_controller).executeUpdate();
		dao.createSQLQuery("truncate table " + DX_TABLE.dx_module).executeUpdate();

		TreeSet<Module> modules = new TreeSet<Module>();
		TreeSet<Controller> controllers = new TreeSet<Controller>();
		TreeSet<Action> actions = this.resolveApiActions();

		for (Action act : actions) {
			controllers.add(act.getController());
			modules.add(act.getController().getModule());
		}
		dao.saveAll(modules);
		dao.saveAll(controllers);
		dao.saveAll(actions);
		dao.flushSession();

		dao.createSQLQuery("delete from " + DX_TABLE.dx_role_action + " where action_id not in (select id from " + DX_TABLE.dx_action + " where level=" + ApiLevel.GRANT_REQUIRED + ")")
				.executeUpdate();

		logger.info("初始化 [Action] 完成。");

		return true;
	}

	public TreeSet<Action> resolveApiActions() throws Exception {
		Package[] packages = new Package[controllerPackages.length];
		int i = 0;
		for (String pkgName : controllerPackages) {
			if (StringUtils.hasText(pkgName)) {
				Package pkg = Package.getPackage(pkgName.trim());
				if (pkg == null) {
					throw new RuntimeException("初始化失败，找不到 package " + pkgName.trim());
				}
				packages[i++] = pkg;
			}
		}
		return resolveApiActions(packages);
	}

	/**
	 * 从注解中解析出 api action 数据
	 * 
	 * @return
	 * @throws Exception
	 */
	public static TreeSet<Action> resolveApiActions(Package[] packages) throws Exception {
		int moduleId = 0;
		int controllerId = 0;
		TreeSet<Action> actions = new TreeSet<Action>();
		ClassPool pool = ClassPool.getDefault();
		pool.insertClassPath(new ClassClassPath(InitApiActionService.class));
		for (Package pkg : packages) {

			// 根据 Package 创建 Module
			ApiModule apiModule = AnnotationUtils.getAnnotation(pkg, ApiModule.class);
			Module module = new Module(++moduleId, apiModule.value(), pkg.getName());

			for (Class<?> clazz : AnnotationUtils.getAnnotation(pkg, SortedControllers.class).value()) {

				// 根据 Class 创建 Controller
				ApiController apiController = AnnotationUtils.getAnnotation(clazz, ApiController.class);
				Controller controller = new Controller(++controllerId, apiController.value(), clazz.getSimpleName(), module);
				module.getControllers().add(controller);

				// 映射请求路径的前缀
				RequestMapping ctrlMapping = AnnotationUtils.getAnnotation(clazz, RequestMapping.class);
				String pathPrefix = ctrlMapping == null ? "" : ctrlMapping.path()[0];

				// 通过 CtClass 和 CtMethod 获取函数定义的行号
				CtClass ctClass = pool.get(clazz.getName());

				for (Method method : clazz.getDeclaredMethods()) {
					ApiAction apiAction = (ApiAction) AnnotationUtils.getAnnotation(method, ApiAction.class);
					if (apiAction == null) {
						continue;
					}
					RequestMapping requestMapping = AnnotationUtils.getAnnotation(method, RequestMapping.class);
					CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName());

					// 取 className.methodName 的 HashCode 绝对值作为 actionId,
					// 多次执行初始化不影响原有的权限配置
					String qualifiedMethodName = clazz.getName() + "." + method.getName();

					// 取行号作为 showOrder
					int showOrder = ctMethod.getMethodInfo().getLineNumber(0) + controller.getId() * 10000;

					// 功能点名字
					String actionName = apiAction.name();

					// 接口路径
					String paths[] = requestMapping.path();
					String path = (paths == null || paths.length == 0) ? pathPrefix : (pathPrefix + paths[0]);

					// RequestBody 中是否包含 json 数据
					int level = apiAction.level().value();

					Action action = new Action(qualifiedMethodName, showOrder, actionName, path, level, controller);
					controller.getActions().add(action);
					actions.add(action);
				}
			}
		}
		return actions;
	}

}
